﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomCmdPaster
{
    /// <summary>
    /// Linyee 2019-10-14
    /// </summary>
    public class LinyeeCmdOptions : ILinyeeCmdOptions
    {
        public ICmd[] Cmds { get; set; }
        public string Doc { get; set; }
    }

    /// <summary>
    /// Linyee 2019-10-14
    /// </summary>
    public class LinyeeCmdOptionsItem : ICmd
    {
        public string CmdName { get; set; }
        public string[] Params { get; set; }
    }

    /// <summary>
    /// Linyee 2019-10-14
    /// </summary>
    public class LinyeeCmd
    {
        private LinyeeCmdOptions options = new LinyeeCmdOptions();
        /// <summary>
        /// 帮助文档
        /// </summary>
        public string Help => options.Doc;

        public LinyeeCmd(Action<ILinyeeCmdOptions> setOpt)
        {
            setOpt(options);
            if (string.IsNullOrEmpty(options.Doc))
            {
                StringBuilder docbd = new StringBuilder();
                docbd.AppendLine("---===帮助文档===---");
                foreach (var cmd in options.Cmds)
                {
                    docbd.AppendLine($"命令：{cmd.CmdName}\t可用参数：{string.Join(" ",cmd.Params.Select(p=>"-"+p))}");
                }
                options.Doc = docbd.ToString();
            }
        }

        /// <summary>
        /// 命令解析
        /// Linyee 2019-10-14
        /// </summary>
        /// <param name="cmdline"></param>
        /// <returns></returns>
        public LinyeeCmdItem Parse(string cmdline,bool isThrowNocmd=true)
        {
            LinyeeCmdItem cmd = new LinyeeCmdItem();
            var ps = ParseState.WaitCmd;
            foreach(var ch in cmdline)
            {
                switch (ps)
                {
                    #region 命令
                    case ParseState.WaitCmd:
                        {
                            if (ch == ' ')
                            {
                                continue;
                            }
                            else
                            {
                                ps = ParseState.AppendingCmd;
                                cmd.AppendCmdChar(ch,ref ps);
                            }
                            break;
                        }
                    case ParseState.AppendingCmd:
                        {
                            cmd.AppendCmdChar(ch, ref ps);
                            break;
                        }
                    #endregion

                    case ParseState.EndedCmd:
                    case ParseState.EndedParamKey:
                    case ParseState.EndedParam:
                        {
                            if (ch == '-')
                            {
                                ps = ParseState.OpenxWithParamKey;
                            }
                            else if (ch == ' ')
                            {
                                continue;
                            }
                            else
                            {
                                cmd.AppendParamChar(ch, ref ps);
                            }
                            break;
                        }
                    case ParseState.OpenxWithParamKey:
                        {
                            cmd.AppendParamKeyChar(ch, ref ps);
                            break;
                        }
                    case ParseState.AppendingParam:
                    case ParseState.StarWithParam:
                    case ParseState.OpenWithParam:
                    case ParseState.OpenxWithParam:
                        {
                            cmd.AppendParamChar(ch, ref ps);
                            break;
                        }
                    default:
                        throw new Exception($"未正确处理{ps}");

                }
            }
            cmd.ParseEnded(ref ps);

            if (!options.Cmds.Select(p=>p.CmdName).Contains(cmd.Cmd,StringComparer.OrdinalIgnoreCase))
            {
                if (isThrowNocmd)
                {
                    throw new Exception($"{cmd.Cmd} 不在指定可用的命令集");
                }
                else
                {
                    return null;
                }
            }

            var paras = options.Cmds.FirstOrDefault(p => p.CmdName.Equals(cmd.Cmd,StringComparison.OrdinalIgnoreCase)).Params;
            foreach (var pa in cmd.Paras.Keys)
            {
                if (!paras.Contains(pa, StringComparer.OrdinalIgnoreCase))
                {
                    throw new Exception($"{pa} 参数名不匹配");
                }
            }

            return cmd;
        }
    }

    /// <summary>
    /// Linyee 2019-10-14
    /// </summary>
    public class LinyeeCmdItem
    {
        public string Cmd { get; set; }
        /// <summary>
        /// 小写化命令
        /// </summary>
        public string CmdLower => Cmd?.ToLower();
        /// <summary>
        /// 大写化命令
        /// </summary>
        public string CmdUpper => Cmd?.ToUpper();

        /// <summary>
        /// 未指定参数名时，，自动以序数4位数为索引 如0001
        /// </summary>
        public SortedDictionary<string, string> Paras { get; set; } = new SortedDictionary<string, string>();

        private StringBuilder cmdbd = new StringBuilder();
        private string tkey = null;
        /// <summary>
        /// 添加到命令
        /// </summary>
        /// <param name="ch"></param>
        /// <param name="ps"></param>
        internal void AppendCmdChar(char ch, ref ParseState ps)
        {
            switch (ps)
            {
                //case ParseState.WaitCmd:
                //    {
                //        if (ch == ' ') { return; }
                //        else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
                //        {
                //            cmdbd.Append(ch);
                //            ps = ParseState.AppendingCmd;
                //        }
                //        break;
                //    }
                case ParseState.AppendingCmd:
                    {
                        if (ch == ' ')
                        {
                            Cmd = cmdbd.ToString();
                            cmdbd.Clear();
                            ps = ParseState.EndedCmd;
                        }
                        else if (ch == '_' || ch == '-' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch >= '\u4E00' && ch <= '\u9FA5')
                        {
                            cmdbd.Append(ch);
                        }
                        else
                        {
                            throw new Exception("不能含有特殊字符");
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 添加到参数值
        /// </summary>
        /// <param name="ch"></param>
        /// <param name="ps"></param>
        internal void AppendParamChar(char ch, ref ParseState ps)
        {
            switch (ps)
            {
                case ParseState.EndedCmd:
                case ParseState.EndedParamKey:
                case ParseState.EndedParam:
                    {
                        if (ch == ' ') { return; }
                        else if (ch == '"') { ps = ParseState.StarWithParam; }
                        else if (ch >= '0' && ch <= '9'||ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '\u4E00' && ch <= '\u9FA5')
                        {
                            cmdbd.Append(ch);
                            ps = ParseState.AppendingParam;
                        }
                        else
                        {
                            throw new Exception("不能含有特殊字符");
                        }
                        break;
                    }
                case ParseState.AppendingParam:
                case ParseState.StarWithParam:
                case ParseState.OpenWithParam:
                    {
                        //空格 结束参数值
                        if (ch == ' ' && ps == ParseState.AppendingParam)
                        {
                            if (tkey == null)
                            {
                                tkey = (Paras.Count+1).ToString("d4");
                            }
                            var tval = cmdbd.ToString();
                            cmdbd.Clear();
                            Paras.Add(tkey, tval);
                            tkey = null;
                            ps = ParseState.EndedParam;
                        }
                        //\
                        else if (ch == '\\' && ps == ParseState.StarWithParam)
                        {
                            ps = ParseState.OpenWithParam;
                            cmdbd.Append(ch);
                        }
                        //" 结束引号开始的参数值
                        else if (ch == '"' && ps == ParseState.StarWithParam)
                        {
                            ps = ParseState.AppendingParam;
                            AppendParamChar(' ', ref ps);
                        }
                        // \"
                        else if (ch == '"' && ps == ParseState.OpenWithParam)
                        {
                            cmdbd.Append(ch);
                            ps = ParseState.StarWithParam;
                        }
                        else if (ch == 'x' && ps == ParseState.OpenWithParam)
                        {
                            cmdbd.Append(ch);
                            ps = ParseState.OpenxWithParam;
                        }
                        else if (ch >= '\x20' || ch <= '\x7E' || ch >= '\u4E00' && ch <= '\u9FA5')
                        {
                            cmdbd.Append(ch);
                            if (ps == ParseState.OpenWithParam) ps = ParseState.StarWithParam;
                        }
                        else
                        {
                            throw new Exception("不能含有特殊字符");
                        }
                        break;
                    }
                case ParseState.OpenxWithParam:
                    {
                        if (ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'Z')
                        {
                            cmdbd.Append(ch);
                        }
                        else
                        {
                            ps = ParseState.StarWithParam;
                            AppendParamChar(ch, ref ps);
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 添加到参数名
        /// </summary>
        /// <param name="ch"></param>
        /// <param name="ps"></param>
        internal void AppendParamKeyChar(char ch, ref ParseState ps)
        {
            switch (ps)
            {
                case ParseState.OpenxWithParamKey:
                    {
                        if (ch == ' ')
                        {
                            tkey = cmdbd.ToString();
                            cmdbd.Clear();
                            ps = ParseState.EndedParamKey;
                        }
                        else if (ch == '_' || ch == '-' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch >= '\u4E00' && ch <= '\u9FA5')
                        {
                            cmdbd.Append(ch);
                        }
                        else
                        {
                            throw new Exception("不能含有特殊字符");
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 结束解析
        /// </summary>
        /// <param name="ps"></param>
        internal void ParseEnded(ref ParseState ps)
        {
            switch (ps)
            {
                case ParseState.AppendingCmd:
                    AppendCmdChar(' ', ref ps);
                    break;
                case ParseState.OpenxWithParamKey:
                    AppendParamKeyChar(' ', ref ps);
                    break;
                case ParseState.AppendingParam:
                    AppendParamChar(' ', ref ps);
                    break;
                case ParseState.EndedCmd:
                case ParseState.EndedParamKey:
                case ParseState.EndedParam:
                    break;
                default:
                    throw new Exception($"未能正确保存解析结果{ps}");
            }
        }
    }


    /// <summary>
    /// Linyee 2019-10-14
    /// </summary>
    public enum ParseState
    {
        /// <summary>
        /// 等待命令
        /// </summary>
        WaitCmd,
        /// <summary>
        /// 添加命令
        /// </summary>
        AppendingCmd,
        /// <summary>
        /// 命令结束
        /// </summary>
        EndedCmd,

        /// <summary>
        /// 打开参数名
        /// </summary>
        OpenxWithParamKey,
        /// <summary>
        /// 参数结束
        /// </summary>
        EndedParamKey,

        /// <summary>
        /// 添加参数值
        /// </summary>
        AppendingParam,
        /// <summary>
        /// 引号开始的值
        /// </summary>
        StarWithParam,
        /// <summary>
        /// 打开16进制转义
        /// </summary>
        OpenxWithParam,
        /// <summary>
        /// 打开转义
        /// </summary>
        OpenWithParam,
        /// <summary>
        /// 参数值结束
        /// </summary>
        EndedParam,
    }
}
